import { Item, ItemDelta } from './type'
import { Factories } from './view'
import { Exports, ITEMS_MAX, STORAGE_SIZE, STORAGE_TYPES, errors, ids, mapSize, mapWidth } from './defs'
import { world } from './store'
import { error } from '@cydon/ui/Message'
import { initRecipes } from './recipes'
import { Coord, updatables } from './tino'

/** 地图大小 */
export let mapHeight = 64

/** 上次数量 */
const lastCount = new Uint32Array(ITEMS_MAX)
/** 上次选中块号 */
let lastSlot = -1

/** 添加物品 */
export function add(name: string, amount: number, slot = -1) {
	const err = addItem(ids.get(name)!, amount, slot)
	if (err)
		error(err)
	else if (slot == -1 && amount > 0)
		world.stats.totalCollect += amount
}
/** 添加物品 */
let addItem: (id: number, amount: number, slot?: number) => string

export let
	/** 初始化游戏 */
	initGame: Exports['initGame'],
	/** 更新物品 */
	updateItems: (items: Item[], slot: number) => void,
	/** 获取图块 */
	getTile: (slot: number) => Uint8Array,
	/** 设置地图数据 */
	put: (slot: number, id: number) => void,
	/** 设置流水线 */
	setProc: (slot: number, recipe: number, period: number) => void,
	/** 更新地图 */
	updateTiles: Exports['updateTiles'],
	/** 添加配方 */
	addRecipe: (deltas: ItemDelta[]) => number,
	/** 尝试应用流水线 */
	tryApply: Exports['tryApply'],
	/** 设置选中物品数量 */
	fill: (items: Item[], slot: number, count?: number) => void,
	/** 保存 */
	save: Exports['save'],
	/** 获取配方 */
	getRecipe: Exports['getRecipe'],
	/** 地图 */
	map: Uint8Array,
	/** 常量表 */
	constants: Uint8ClampedArray

/** 获取地形 */
export const getTerrain = (slot: number) =>
	getTile(slot)[0] & 7

/** 获取坐标 */
export const toCoord = (slot: number): Coord => [slot % mapWidth, slot / mapWidth | 0]

const url = import.meta.env.VITE_WASM_URL

export async function loadWasm(setTex: typeof Factories['prototype']['setTexture']) {
	let buffer: ArrayBuffer
	const { instance } = await WebAssembly.instantiateStreaming(fetch(url), {
		env: {
			floor: Math.floor,
			sin: Math.sin,
			write(p: number, size: number) {
				// 深拷贝
				world.data.push(new Uint8Array(buffer, p, size).slice())
			},
		}
	})
	const { initGame,
		map: { value: m },
		constants: c,
		getStorage: g,
		storageType,
		addItem: a,
		addItemDelta,
		nextProc,
		put: putBuild,
		setProc: sp,
		save: s,
		tryApply: t,
		getRecipe: gr,
		updateNet,
		memory,
		updateTiles: u } = <Exports>instance.exports
	buffer = memory.buffer
	// 初始化游戏
	{
		const { data } = world
		let p = initGame(world.seed)
		map = new Uint8Array(buffer, m, mapSize * 4)
		initMap(map)
		for (; p; p = initGame(0)) {
			const buf = data.shift()
			if (buf)
				new Uint8Array(buffer, p, buf.length).set(buf)
		}
	}
	let procId = nextProc()
	save = s
	updateTiles = u
	getRecipe = gr
	u(0, mapWidth, mapHeight)
	constants = new Uint8ClampedArray(buffer, c.value, 256 * 8 * 3)
	const getStorage = (slot: number) => {
		const p = g(slot)
		if (!p)
			return null
		const s = new Uint32Array(buffer, p, STORAGE_SIZE)
		return {
			coord: toCoord(slot),
			capacity: s.subarray(0, STORAGE_TYPES),
			count: s.subarray(STORAGE_TYPES)
		}
	}
	setProc = (slot, recipe, period) => {
		console.log('setProc', slot, recipe, period)
		const err = sp(slot, recipe, period)
		if (err)
			error(errors[err])
	}
	updateItems = (items, slot) => {
		let s = getStorage(slot)
		if (!s)
			s = getStorage(slot = -1)!
		if (slot != lastSlot) {
			lastCount.set(s.count)
			lastSlot = slot
		}
		for (let i = 0; i < ITEMS_MAX; i++) {
			items[i].amount = s.count[i]
			items[i].delta = s.count[i] - lastCount[i]
			items[i].capacity = s.capacity[storageType(i)]
		}
		lastCount.set(s.count)
	}
	addItem = (id, delta, slot = -1) => {
		let err = a(id, delta, slot)
		if (err == 3)
			err = a(id, delta, -1)
		return err ? errors[err] : ''
	}
	fill = (items, slot, count = 0) => {
		let s = getStorage(slot)
		if (!s)
			s = getStorage(slot = -1)!
		for (let i = 0; i < items.length; i++) {
			if (items[i].selected) {
				s.count[i] = count < 0 ? s.capacity[storageType(i)] : count
			}
		}
	}
	addRecipe = (deltas) => {
		for (const [id, delta] of deltas)
			if (!addItemDelta(id, delta))
				return -1
		const id = procId
		procId = nextProc()
		return id
	}
	tryApply = t
	getTile = (slot) => {
		const i = slot * 4
		return map.subarray(i, i + 4)
	}
	put = (slot, value) => {
		const err = putBuild(slot, value)
		if (err)
			error(errors[err])
		else
			setTex(1, 'map', map)
	}
	setTex(2, 'constants', constants)
	initRecipes()
	// 每帧更新网络
	updatables.add(tick => {
		updateNet(tick)
		setTex(1, 'map', map)
	})
}

/** 初始化地图 */
function initMap(map: Uint8Array) {
	if (world.map) {
		const size = world.mapSize ** 2
		for (let i = 0; i < size; i++) {
			map[i * 4 + 2] = world.map[i << 1]
			map[i * 4 + 3] = world.map[i << 1 | 1]
		}
	}
	world.map = map
}
